package main

import (
	"fmt"
	"net"
	"strconv"
	"time"
)

const Protocol = "tcp" // 通讯协议
const Delay = 200      // 延迟
const NodeNum = 50     // 节点数
const MaxConn = 8      // 节点最多连接节点数

var NodeAddrs []string = make([]string, NodeNum) // 存放系统中的节点 ip:port
var IsAcceptMsg = make(map[string]string)        // 存放系统中节点接受数据的缓存
var NodeMapping = make(map[string][]string)      // 存放节点之间映射关系

func main() {
	// 组织系统中存在的节点地址
	for i := 0; i < NodeNum; i++ {
		nodeAddr := "localhost:" + strconv.Itoa(8000+i)
		NodeAddrs[i] = nodeAddr
	}

	fmt.Println(NodeAddrs)

	// 节点映射关系[1个节点最多只连接8个节点]
	for index, nodeAddr := range NodeAddrs {
		if MaxConn > len(NodeAddrs[index:]) {
			// 拿到超出的索引数目
			tmp := MaxConn - len(NodeAddrs[index:])
			//NodeMapping[nodeAddr] = append(NodeMapping[nodeAddr], NodeAddrs[index-tmp:index]...)
			NodeMapping[nodeAddr] = append(NodeMapping[nodeAddr], NodeAddrs[:tmp]...)
			NodeMapping[nodeAddr] = append(NodeMapping[nodeAddr], NodeAddrs[index:]...)
		} else {
			NodeMapping[nodeAddr] = append(NodeMapping[nodeAddr], NodeAddrs[index:index+MaxConn]...)
		}
	}
	fmt.Printf("%v\n", NodeMapping)

	// 组织系统中节点接受数据的缓存
	for _, nodeAddr := range NodeAddrs {
		IsAcceptMsg[nodeAddr] = ""
	}

	StartServer()

	for { // 阻塞程序实现
		time.Sleep(60 * time.Second)
	}
}

func StartServer() {
	for _, item := range NodeAddrs {
		go server(item)
	}
}

func server(nodeAddr string) {
	// 创建服务端
	listener, err := net.Listen(Protocol, nodeAddr)
	fmt.Printf("端口节点 < %s > 已启动...\n", nodeAddr)
	if err != nil {
		fmt.Println("服务器 listen err: ", err)
	}
	defer listener.Close()

	for {
		// 等待客户端连接请求
		conn, err := listener.Accept()

		if err != nil {
			fmt.Println("accept err:", err)
			return
		}
		// 处理用户请求，新建一个协程
		go HandleMulServerConn(conn, nodeAddr)
	}
}

// 处理发来的请求，实现多个服务端之间通讯
func HandleMulServerConn(conn net.Conn, nodeAddr string) {
	// 函数调用完毕，自动关闭conn
	defer conn.Close()

	buf := make([]byte, 2048)
	n, err := conn.Read(buf) // 读取用户数据
	if err != nil {
		fmt.Println("read err:", err)
		return
	}

	// 判断发过来的数据是否与上次数据一致，如果一致，直接退出;
	// 如果不一致，进行修改，并广播给其他节点
	if string(buf[:n]) == IsAcceptMsg[nodeAddr] {
		//fmt.Println("<" + nodeAddr + "> 已经收到过重复数据")
		return
	}

	fmt.Println("<" + nodeAddr + "> 收到数据:" + string(buf[:n]))

	IsAcceptMsg[nodeAddr] = string(buf[:n]) // 标记为该节点已经收到过请求

	// 模拟延迟
	time.Sleep(Delay * time.Millisecond) // 设置延迟
	// 将收到的数据广播给连接的8个节点(循环发送)
	for _, item := range NodeMapping[nodeAddr] {
		// 过滤到当前节点，给其他节点发送请求(建立客户端)
		if item == nodeAddr {
			continue
		}

		// 建立客户端连接其他节点并发送，广播该该节点收到的数据
		clientConn, err := net.Dial(Protocol, item)
		if err != nil {
			fmt.Println("clientConn err:", item)
			return
		}
		defer clientConn.Close()

		// 发送数据
		_, err = clientConn.Write(buf[:n])

		if err != nil {
			fmt.Println("Write data err:", err)
			return
		}
	}
}
